/*
 ***************************************************************************************
 *  Copyright (C) 2006 EsperTech, Inc. All rights reserved.                            *
 *  http://www.espertech.com/esper                                                     *
 *  http://www.espertech.com                                                           *
 *  ---------------------------------------------------------------------------------- *
 *  The software in this package is published under the terms of the GPL license       *
 *  a copy of which has been included with this distribution in the license.txt file.  *
 ***************************************************************************************
 */
package com.espertech.esper.runtime.internal.timer;

import com.espertech.esper.common.internal.schedule.TimeSourceService;

/**
 * Allow for different strategies for getting VM (wall clock) time.
 * See JIRA issue ESPER-191 Support nano/microsecond resolution for more
 * information on Java system time-call performance, accuracy and drift.
 *
 * @author Jerry Shea
 */
public class TimeSourceServiceImpl implements TimeSourceService {
    private static final long MICROS_TO_MILLIS = 1000;
    private static final long NANOS_TO_MICROS = 1000;

    /**
     * A public variable indicating whether to use the System millisecond time or
     * nano time, to be configured through the runtimesettings.
     */
    public static boolean isSystemCurrentTime = true;

    private final long wallClockOffset;
    private final String description;

    /**
     * Ctor.
     */
    public TimeSourceServiceImpl() {
        this.wallClockOffset = System.currentTimeMillis() * MICROS_TO_MILLIS - this.getTimeMicros();
        this.description = String.format("%s: resolution %d microsecs",
                this.getClass().getSimpleName(), this.calculateResolution());
    }

    /**
     * Convenience method to get time in milliseconds
     *
     * @return wall-clock time in milliseconds
     */
    public long getTimeMillis() {
        if (isSystemCurrentTime) {
            return System.currentTimeMillis();
        }
        return getTimeMicros() / MICROS_TO_MILLIS;
    }

    private long getTimeMicros() {
        return (System.nanoTime() / NANOS_TO_MICROS) + wallClockOffset;
    }


    /**
     * Calculate resolution of this timer in microseconds i.e. what is the resolution
     * of the underlying platform's timer.
     *
     * @return timer resolution
     */
    protected long calculateResolution() {
        final int loops = 5;
        long totalResolution = 0;
        long time = this.getTimeMicros(), prevTime = time;
        for (int i = 0; i < loops; i++) {
            // wait until time changes
            while (time == prevTime)
                time = this.getTimeMicros();
            totalResolution += time - prevTime;
            prevTime = time;
        }
        return totalResolution / loops;
    }

    @Override
    public String toString() {
        return description;
    }
}
